﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Net.Mail;
using System.Reflection;

namespace SanctusSoftware.Logging
{

    #region Logger

    public class ExceptionLogger
    {

        // TODO: Have a general way of noting any general information about the configuration, such as databases, etc.

        #region Constructor

        static ExceptionLogger()
        {
            logItems = new List<LogItem>();
            distributors = new Dictionary<string, ILogDistributor>();
            globalAdditionalData = new Dictionary<string, string>();
        }

        #endregion

        #region Properties

        private static List<LogItem> logItems = null;

        public static LogItem[] LogItems
        {
            get { return logItems.ToArray(); }
        }

        #endregion

        #region Distributors

        private static Dictionary<string, ILogDistributor> distributors = null;

        public static void AddLogDistributor(string key, ILogDistributor distributor)
        {
            if (LogDistributorExists(key))
            {
                throw new Exception("A LogDistributor already exists with the key '" + key + "'");
            }

            if (distributor == null)
            {
                throw new ArgumentNullException("The distributor parameter cannot be null");
            }

            distributors.Add(key, distributor);

        }

        public static ILogDistributor GetLogDistributor(string key)
        {
            return distributors[key];
        }

        public static bool LogDistributorExists(string key)
        {
            return distributors.ContainsKey(key);
        }

        public static void RemoveLogDistributor(string key)
        {
            distributors.Remove(key);
        }

        #endregion

        #region Additional Data

        private static Dictionary<string, string> globalAdditionalData = null;

        public static void AddGlobalAdditionalData(string key, string value)
        {
            globalAdditionalData.Add(key, value);
        }

        #endregion

        #region Log

        public static void Log(Exception exception)
        {
            Log(exception, string.Empty);
        }

        public static void Log(Exception exception, string additionalData)
        {
            KeyValuePair<string, string> temp = new KeyValuePair<string, string>("", additionalData);
            Log(exception, new KeyValuePair<string, string>[] { temp });
        }

        public static void Log(Exception exception, KeyValuePair<string, string>[] additionalData)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            foreach (KeyValuePair<string, string> kvp in additionalData)
            {
                dict.Add(kvp.Key, kvp.Value);
            }
            Log(exception, dict);
        }

        public static void Log(Exception exception, Dictionary<string, string> additionalData)
        {

            try
            {
                LogItem l = new LogItem();
                l.UserName = System.Environment.UserDomainName;
                l.ComputerName = System.Environment.MachineName;
                l.OperatingSystem = System.Environment.OSVersion.VersionString;

                l.StackTrace = System.Environment.StackTrace;

                l.AdditionalData = GetAdditionalDataString(additionalData);

                l.Exceptions = GetExceptionString(exception);

                l.Assemblies = GetAssembliesString();

                logItems.Add(l);

                // Loop through the distributors and distribute the error log
                foreach (ILogDistributor distributor in distributors.Values)
                {

                    try
                    {
                        distributor.Distribute(l);
                    }
                    catch (Exception ex)
                    {
                        // DO NOTHING (what can be done?)    
                    }

                }

            }
            catch (Exception ex)
            {
                // DO NOTHING (what can be done?)
            }
        }

        #endregion

        #region Build Strings

        private static string GetAssembliesString()
        {

            StringBuilder sb = new StringBuilder();

            AppDomain currentAppDomain = AppDomain.CurrentDomain;
            
            List<Assembly> assemblies = new List<Assembly>();
            assemblies.AddRange(currentAppDomain.GetAssemblies());
            assemblies.Sort(delegate(Assembly a1, Assembly a2)
            {
                return a1.FullName.CompareTo(a2.FullName);
            });

            foreach (Assembly assembly in assemblies)
            {
                sb.AppendLine(assembly.FullName);
            }

            return sb.ToString();

        }

        private static string GetExceptionString(Exception exception)
        {
            StringBuilder exceptionStringBuilder = new StringBuilder();

            Exception temp = exception;

            while (temp != null)
            {
                if (temp == exception)
                {
                    exceptionStringBuilder.AppendLine("Main Exception");
                }
                else
                {
                    exceptionStringBuilder.AppendLine("Inner Exception");
                }

                exceptionStringBuilder.AppendLine("Exception Type: " + temp.GetType().Name);
                exceptionStringBuilder.AppendLine("Message: " + temp.Message);
                exceptionStringBuilder.AppendLine("Source: " + temp.Source);
                exceptionStringBuilder.AppendLine("Stack Trace: " + temp.StackTrace);
                exceptionStringBuilder.AppendLine("Exception Data:");
                if (temp != null && temp.Data != null)
                {
                    foreach (object key in temp.Data.Keys)
                    {
                        exceptionStringBuilder.Append(key.ToString() + ": " + temp.Data[key].ToString());
                    }
                }

                exceptionStringBuilder.AppendLine();

                temp = temp.InnerException;
            }

            string exceptionString = exceptionStringBuilder.ToString();
            return exceptionString;
        }

        private static string GetAdditionalDataString(Dictionary<string, string> additionalData)
        {

            StringBuilder sb = new StringBuilder();

            // Global Additional Data
            foreach (string key in globalAdditionalData.Keys)
            {
                sb.AppendLine(key + ": " + globalAdditionalData[key]);
            }

            // Additional Data
            if (additionalData != null)
            {


                foreach (string key in additionalData.Keys)
                {
                    sb.AppendLine(key + ": " + additionalData[key]);
                }



            }



            return sb.ToString();
        }

        #endregion

        public static void Test()
        {
            try
            {
                throw new Exception("Test Exception");
            }
            catch (Exception ex)
            {
                Log(ex);
            }
        }

    }

    #endregion

    #region Log Item

    public class LogItem
    {

        #region Public Properties

        public string UserName { get; set; }
        public string ComputerName { get; set; }
        public string OperatingSystem { get; set; }

        public string AdditionalData { get; set; }

        public string StackTrace { get; set; }

        public string Exceptions { get; set; }

        public string Assemblies { get; set; }

        #endregion

    }

    #endregion

    #region Log Distributors


    public interface ILogDistributor
    {
        void Distribute(LogItem logItem);
    }

    public abstract class TextLogDistributor : ILogDistributor
    {

        protected TextLogDistributor()
        {
            IncludeComputerName = false;
            IncludeUserName = false;
        }

        #region Public Properties

        public bool IncludeComputerName { get; set; }
        public bool IncludeUserName { get; set; }

        #endregion

        #region ILogDistributor Members

        public void Distribute(LogItem logItem)
        {

            StringBuilder sb = new StringBuilder();

            sb.AppendLine("Date/Time: " + DateTime.Now.ToString());
            sb.AppendLine("Operating System: " + logItem.OperatingSystem);
            if (IncludeComputerName)
            {
                sb.AppendLine("Computer Name: " + logItem.ComputerName);    
            }
            if (IncludeUserName)
            {
                sb.AppendLine("User Name: " + logItem.UserName);    
            }

            sb.AppendLine();
            sb.AppendLine("---------- Stack Trace ----------");
            sb.AppendLine(logItem.StackTrace);

            sb.AppendLine();
            sb.AppendLine("---------- Exceptions ----------");
            sb.AppendLine(logItem.Exceptions);

            sb.AppendLine();
            sb.AppendLine("---------- Assemblies ----------");
            sb.AppendLine(logItem.Assemblies);

            sb.AppendLine();
            sb.AppendLine("---------- Additional Data ----------");
            sb.AppendLine(logItem.AdditionalData);

            Distribute(sb.ToString());

        }

        #endregion

        public abstract void Distribute(string logText);


    }

    public class NotepadLogDistributor : TextLogDistributor
    {

        public override void Distribute(string logText)
        {

            FileInfo f = new FileInfo(Path.GetTempFileName());
            StreamWriter sw = f.CreateText();
            sw.Write(logText);
            sw.Close();

            Process notePad = new Process();
            notePad.StartInfo.FileName = "notepad.exe";
            notePad.StartInfo.Arguments = "\"" + f.FullName + "\"";
            notePad.Start();


        }
    }

    public class EmailLogDistributor : TextLogDistributor
    {



        #region Public Properties

        public string FromEmailAddress { get; set; }
        public string FromDisplayName { get; set; }
        public string ToEmailAddresses { get; set; }
        public string Subject { get; set; }

        public string SmtpServer { get; set; }
        public int SmtpPort { get; set; }
        public string SmtpUserName { get; set; }
        public string SmtpPassword { get; set; }

        #endregion


        public override void Distribute(string logText)
        {

            MailMessage mail = new MailMessage();
            mail.From = new MailAddress(this.FromEmailAddress, this.FromDisplayName);
            mail.To.Add(this.ToEmailAddresses);
            mail.Subject = this.Subject;
            mail.Body = logText;

            SmtpClient smtpClient = new SmtpClient();
            smtpClient.Host = this.SmtpServer;
            smtpClient.Port = this.SmtpPort;
            smtpClient.Credentials = new System.Net.NetworkCredential(this.SmtpUserName, this.SmtpPassword);
            smtpClient.Send(mail);

        }

    }

    public class TextFileLogDistributor : TextLogDistributor
    {

        #region Public Properties

        public DirectoryInfo LogFolder { get; set; }

        #endregion

        public override void Distribute(string logText)
        {

            if (LogFolder == null)
            {
                throw new InvalidOperationException("Cannot distribute when the LogFolder property is null");
            }

            if (!LogFolder.Exists)
            {
                try
                {
                    LogFolder.Create();
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("The LogFolder " + LogFolder.FullName + " does not exist and Could Not Be Created.");    
                    
                }
                
            }

            string path = Path.Combine(LogFolder.FullName, DateTime.Now.ToString("yyyy-MM-dd hh-mm-ss-fffff") + " " + Guid.NewGuid().ToString() + ".txt");
            FileInfo f = new FileInfo(path);
            StreamWriter sw = f.CreateText();
            sw.Write(logText);
            sw.Close();

        }

    }



    #endregion

}